package com.orion.service.original;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.*;
import co.elastic.clients.elasticsearch._types.query_dsl.*;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.search.HighlightField;
import co.elastic.clients.elasticsearch.indices.*;
import co.elastic.clients.json.JsonData;
import co.elastic.clients.util.ObjectBuilder;
import com.orion.entity.*;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.function.Function;

/**
 * @author Administrator
 */
@Service
public class EsService implements IEsService {

    @Resource(name = "esClient")
    private ElasticsearchClient esClient;

/*    @Resource(name = "highClient")
    private RestHighLevelClient highClient;*/

    @Override
    public DeleteIndexResponse deleteIndex(String idx) throws IOException {
        DeleteIndexResponse deleteIndexResponse = esClient.indices()
                .delete(new DeleteIndexRequest.Builder().index(idx).build());
        return deleteIndexResponse;
    }

    /**
     * 简单演示，用代码创建具体的索引非常麻烦，建议还是使用DSL编写执行
     *
     * @param idx
     * @param numberOfShards
     * @param numberOfReplicas
     * @return
     * @throws IOException
     */
    @Override
    public CreateIndexResponse createIndex(String idx, Integer numberOfShards, Integer numberOfReplicas) throws IOException {
        Function<IndexSettings.Builder, ObjectBuilder<IndexSettings>> setFn = settingBuilder -> {
            settingBuilder.numberOfReplicas(numberOfReplicas.toString());
            settingBuilder.numberOfShards(numberOfShards.toString());
            return settingBuilder;
        };

        CreateIndexRequest.Builder builder = new CreateIndexRequest.Builder();
        CreateIndexRequest request = builder.index(idx).settings(setFn).build();
        return esClient.indices().create(request);
    }

    @Override
    public GetMappingResponse mapping(String idx) throws IOException {
        GetMappingResponse mapping = esClient.indices().getMapping(builder -> {
            builder.index(idx);
            return builder;
        });
        return mapping;
    }


    /**
     * 根据id查询doc
     *
     * @param idx
     * @param id
     * @param clazz
     * @param <T>
     * @return
     * @throws IOException
     */
    @Override
    public <T> GetResponse<T> queryDocById(String idx, Long id, Class<T> clazz) throws IOException {
        GetResponse<T> buildingGetResponse = esClient.get(getBuilder -> {
            getBuilder.index(idx).id(id.toString());
            return getBuilder;
        }, clazz);

        return buildingGetResponse;
    }

    /**
     * upsert 对于传输的参数，存在就更新，不存在就不更新
     *
     * @param idx
     * @param building
     * @return
     * @throws IOException
     */
    @Override
    public UpdateResponse<Building> update(String idx, Building building) throws IOException {
        UpdateRequest.Builder<Building, Building> builder = new UpdateRequest.Builder<>();
        builder.index(idx)
                .id(building.getId().toString())
                .doc(building)
                .upsert(building);
        return esClient.update(builder.build(), Building.class);
    }

    /**
     * 多次提交，新增或覆盖，version会增加
     *
     * @param idx
     * @param building
     * @return
     * @throws IOException
     */
    @Override
    public IndexResponse addDoc(String idx, Building building) throws IOException {
        IndexRequest.Builder<Building> indexRequest = new IndexRequest.Builder<>();
        IndexRequest<Building> build = indexRequest.index(idx)
                .id(building.getId().toString())
                .document(building).build();
        return esClient.index(build);
    }

    /**
     * 根据id删除doc
     *
     * @param idx
     * @param id
     * @return
     * @throws IOException
     */
    @Override
    public DeleteResponse deleteDoc(String idx, Long id) throws IOException {
        DeleteRequest deleteRequest = DeleteRequest.of(d -> d.index(idx).id(id.toString()));
        return esClient.delete(deleteRequest);
    }

    /**
     * GET /building/_doc/_search
     * {
     * 	"from": 0,
     * 	"highlight": {
     * 		"fields": {
     * 			"name": {
     * 				"post_tags": ["</font>"],
     * 				"pre_tags": ["<font color='read'"]
     *                        } 		}
     *    },
     * 	"query": {
     * 		"bool": {
     * 			"must": [{
     * 				"match": {
     * 					"name": {
     * 						"query": "广东"
     *                    }
     *                }
     *            }],
     * 			"should": [{
     * 				"range": {
     * 					"history_years": {
     * 						"lt": 17
     *                    }
     *                }
     *            }, {
     * 				"range": {
     * 					"create_time": {
     * 						"gte": "2007-06-01"
     *                    }
     *                }
     *            }]
     *        }
     *    },
     * 	"size": 20,
     * 	"sort": [{
     * 		"create_time": {
     * 			"order": "desc"
     *        }
     *    }, {
     * 		"history_years": {
     * 			"order": "asc"
     *        }
     *    }]
     * }
     * 深分页
     *
     * @param idx
     * @param pageSize
     * @param from
     * @param building
     * @return
     */
    @Override
    public SearchResponse<Building> queryDeepPage(String idx, int pageSize, int from, Building building) throws IOException {
        SearchRequest.Builder builder = new SearchRequest.Builder();
        builder.index(idx);
        builder.size(pageSize);
        builder.from(from);
        builder.query(Query.of(qb -> {
            qb.bool(BoolQuery.of(bb -> {
                bb.must(Query.of(mqb ->
                        mqb.match(MatchQuery.of(mq ->
                                mq.field("name").query(FieldValue.of(building.getName()))))));
                //should多个并列条件,不能在一个query里面写，不然覆盖
                bb.should(Query.of(s1 -> s1.range(sr -> sr.field("history_years").lt(JsonData.of(building.getHistoryYears())))));
                bb.should(Query.of(s2 -> s2.range(sr -> sr.field("create_time").gte(JsonData.of(building.getCreateTime())))));
                return bb;
            }));
            return qb;
        }));
        builder.sort(s -> s.field(v -> v.field("create_time").order(SortOrder.Desc)));
        builder.sort(s -> s.field(v -> v.field("history_years").order(SortOrder.Asc)));

        builder.highlight(h -> h
                .fields("name",  HighlightField.of(v -> v.postTags("</font>").preTags("<font color='read'"))));
        return esClient.search(builder.build(), Building.class);
    }

    /**
     * GET /building/_doc/_search
     * {
     *   "query": {
     *     "bool": {
     *       "should": [
     *         {
     *           "bool": {
     *             "must": [
     *               {"match": {"name": "广东"}},
     *               {"range": {"history_years": {"gt": 4}}}
     *               ]
     *             ,
     *             "must_not": {
     *               "range": {"create_time": {"lte": "2007-01-01"}}
     *             }
     *           }
     *         }
     *         ,
     *         {
     *           "term": {"shortname": "滕王阁"}
     *         }
     *       ]
     *     }
     *   }
     * }
     *  组合bool查询,bool嵌套bool
     * @param idx
     * @param building
     * @return
     * @throws IOException
     */
    @Override
    public SearchResponse<Building> queryComposite(String idx, Building building) throws IOException {
        SearchRequest.Builder builder = new SearchRequest.Builder();
        builder.index(idx);
        builder.query(Query.of(q ->
                q.bool(b-> {
                    b.should(Query.of(sq -> sq.bool(BoolQuery.of(bq -> {
                        bq.must(m -> m.match(MatchQuery.of(mq -> mq.field("name").query(FieldValue.of(building.getName())))));
                        bq.must(m -> m.range(RangeQuery.of(r -> r.field("history_years").gt(JsonData.of(building.getHistoryYears())))));
                        bq.mustNot(Query.of(rq -> rq.range(RangeQuery.of(r -> r.field("create_time").lte(JsonData.of(building.getCreateTime()))))));
                        return bq;
                    }))));
                    b.should(Query.of(sq -> sq.term(TermQuery.of(tq -> tq.field("shortname").value(FieldValue.of(building.getShortname()))))));
                    return b;
                })
        ));
        return esClient.search(builder.build(), Building.class);
    }

    /**
     * 简单聚合
     *
     * @param idx
     * @return
     * @throws IOException
     */
    @Override
    public SearchResponse<Car> aggs(String idx) throws IOException {
        SearchRequest.Builder builder = new SearchRequest.Builder();
        builder.index(idx);
        SearchRequest.Builder aggregations = builder.size(0).aggregations("my_group_make", b -> b.terms(t -> t.field("make")));
        return esClient.search(aggregations.build(), Car.class);
    }

    /**
     * GET /cars/_doc/_search
     * {
     *   "aggs":{
     *     "my_group_make":{
     *       "terms":{"field":"make"},
     *       "aggs":{
     *         "my_avg_price":
     *           {"avg":{"field":"price"}}
     *
     *       }
     *     }
     *   }
     * }
     * 多个聚合，父子聚合,分组+统计
     *
     * @param idx
     * @return
     * @throws IOException
     */
    @Override
    public SearchResponse<Car> aggsComposite(String idx) throws IOException {
        SearchRequest.Builder builder = new SearchRequest.Builder();
        builder.index(idx);
        builder.size(0);
        builder.aggregations("my_group_make"
                , a -> a.terms(TermsAggregation.of(v -> v.field("make")))
                        .aggregations("my_avg_price"
                                , g -> g.avg(AverageAggregation.of(x -> x.field("price")))));
        return esClient.search(builder.build(), Car.class);
    }

    /**
     *GET /cars/_doc/_search
     * {
     *   "size":0,
     *   "aggs": {
     *     "my_gourp_make": {
     *       "terms":{"field": "make"},
     *       "aggs":{
     *         "my_group_color":{
     *           "terms":{"field":"color"}
     *         }
     *       }
     *     }
     *   }
     * }
     *
     * 父子聚合，分组+分组
     *
     * @param idx
     * @return
     * @throws IOException
     */
    @Override
    public SearchResponse<Car> aggsMultiGroup(String idx) throws IOException {
        SearchRequest.Builder builder = new SearchRequest.Builder();
        builder.index(idx);
        builder.size(0);
        builder.aggregations("my_group_make"
                , a -> a.terms(TermsAggregation.of(v -> v.field("make")))
                        .aggregations("my_group_color"
                                , g -> g.terms(TermsAggregation.of(x -> x.field("color")))));
        return esClient.search(builder.build(), Car.class);
    }

    /**
     * GET /cars/_doc/_search
     * {
     *   "size":0,
     *   "aggs":{
     *     "my_group_make":{
     *       "terms":{"field":"make"},
     *       "aggs":{
     *         "my_max_price":{"max":{"field":"price"}},
     *         "my_group_color":{"terms":{"field":"color"}}
     *       }
     *     }
     *   }
     * }
     * 以make分组统计其最大价格以及车的颜色分组数量,最贵和颜色是等级关系
     */
    @Override
    public SearchResponse<Car> aggsBros(String idx) throws IOException {
        SearchRequest.Builder builder = new SearchRequest.Builder();
        builder.index(idx);
        builder.size(0);
        builder.aggregations("my_group_make"
                , a -> a.terms(TermsAggregation.of(v -> v.field("make")))
                        .aggregations("my_group_color", g -> g.terms(TermsAggregation.of(x -> x.field("color"))))
                        .aggregations("my_max_price", g -> g.max(MaxAggregation.of(x -> x.field("price"))))
        );
        return esClient.search(builder.build(), Car.class);
    }


    /**
     * GET /cars/_doc/_search
     * {
     *   "size":0,
     *   "aggs":{
     *     "my_group_color":{
     *       "terms":{
     *         "field":"color"
     *       },
     *       "aggs":{
     *         "my_avg_price":{
     *             "avg":{
     *               "field":"price"
     *             }
     *         },
     *         "my_group_make":{
     *           "terms":{
     *             "field":"make"
     *           },
     *           "aggs":{
     *             "max_price":{"max":{"field":"price"}},
     *             "min_price":{"min":{"field":"price"}}
     *           }
     *         }
     *       }
     *     }
     *   }
     * }
     * #####复合版,先颜色分组，在求得各颜色的平均加钱，（平级关系）各个颜色的品牌的最大最小价格
     * @param idx
     * @return
     * @throws IOException
     */
    @Override
    public SearchResponse<Car> complexAggs(String idx) throws IOException {
        SearchRequest.Builder builder = new SearchRequest.Builder();
        builder.index(idx);
        builder.size(0);
        builder.aggregations("my_group_color", Aggregation.of(a ->
                a.terms(TermsAggregation.of(t -> t.field("color")))
                        .aggregations("my_avg_price", Aggregation.of(r -> r.avg(v -> v.field("price"))))
                        .aggregations("my_group_make", Aggregation.of(r -> r
                                        .terms(e -> e.field("make"))
                                        .aggregations("max_price", Aggregation.of(g -> g.max(MaxAggregation.of(m -> m.field("price")))))
                                        .aggregations("min_price",Aggregation.of(g->g.min(MinAggregation.of(min -> min.field("price")))))
                                )
                        )
        ));
        return esClient.search(builder.build(), Car.class);
    }

    /**
     *
     * GET /cars/_doc/_search
     * {
     *   "query": {
     *     "term": {
     *       "make": "honda"
     *     }
     *   },
     *   "aggs": {
     *     "filterAndAggs": {
     *       "filter": {
     *         "range": {
     *           "sold": {
     *             "from": "2014-11-01"
     *           }
     *         }
     *       },
     *       "aggs": {
     *         "totalSum": {
     *           "sum": {
     *             "field": "price"
     *           }
     *         }
     *       }
     *     }
     *   }
     * }
     * #####过滤、搜索、聚合,先查出honda车，在选出2014-11-01后sold的车的总销售额
     */
    @Override
    public SearchResponse<Car> aggsFilterQuery(String idx,Car car) throws IOException {
        SearchRequest.Builder builder = new SearchRequest.Builder();
        builder.index(idx);

        builder.query(Query.of(q -> q.term(t -> t.field("make").value(FieldValue.of(car.getMake())))));

        builder.aggregations("filterAndAggs", f -> f
                .filter(Query.of(q -> q.range(RangeQuery.of(r -> r.field("sold").from(JsonData.of(car.getSold()).toString())))))
                .aggregations("totalSum", sf -> sf.sum(SumAggregation.of(sum -> sum.field("price"))))
        );

        return esClient.search(builder.build(), Car.class);
    }


    /**
     * 建议搜索
     *
     * @param idx
     * @return
     * @throws IOException
     */
    @Override
    public SearchResponse<Suggestion> suggest(String idx, String prefix) throws IOException {
        SearchRequest.Builder builder = new SearchRequest.Builder();
        builder.index(idx);

        builder.suggest(fn -> fn
                .suggesters("xx",f1 ->
                        {
                            f1.prefix(prefix);
                            f1.completion(c -> {
                                c.field("suggest");
                                return c;
                            });
                            return f1;
                        })
                );
        return esClient.search(builder.build(), Suggestion.class);
    }

    /**
     *
     * #####嵌套查询与嵌套聚合
     * GET /nested_test/_doc/_search
     * {
     *   "query": {
     *     "bool": {
     *       "must": {
     *         "nested": {
     *           "path": "comments",
     *           "query": {
     *             "bool": {
     *               "must": [
     *                 {
     *                   "match": {
     *                     "comments.name": "alice"
     *                   }
     *                 },
     *                 {
     *                   "match": {
     *                     "comments.age": 28
     *                   }
     *                 }
     *               ]
     *             }
     *           }
     *         }
     *       }
     *     }
     *   },
     *   "aggs": {
     *     "mycomments": {
     *       "nested": {
     *         "path": "comments"
     *       },
     *       "aggs": {
     *         "totalComStar": {
     *           "sum": {
     *             "field": "comments.stars"
     *           }
     *         }
     *       }
     *     }
     *   }
     * }
     * 级联搜索
     *
     * @param idx
     * @return
     * @throws IOException
     */
    @Override
    public SearchResponse<NestObj> nested(String idx, Comment comment) throws IOException {
        SearchRequest.Builder builder = new SearchRequest.Builder();
        builder.index(idx);
        builder.query(Query.of(q
                -> q.bool(BoolQuery.of(bq
                -> bq.must(ms
                -> ms.nested(NestedQuery.of(n
                -> n.path("comments").query(Query.of(nq
                        -> nq.bool(BoolQuery.of(
                                nbq -> {
                                    nbq.must(v -> v.match(nm -> nm.field("comments.name").query(FieldValue.of(comment.getName()))));
                                    nbq.must(v-> v.match(nm->nm.field("comments.age").query(FieldValue.of(comment.getAge()))));
                                    return nbq;
                                })
                )
        )))))))));

        builder.aggregations("mycomments"
                , a -> a.nested(NestedAggregation.of(n -> n.path("comments")))
                        .aggregations("totalComStar",Aggregation.of(as -> as.sum(SumAggregation.of(sum -> sum.field("comments.stars")))))
        );
        return esClient.search(builder.build(), NestObj.class);
    }
}
